package com.xjrsoft.common.helper;

import com.alibaba.fastjson.JSONObject;
import com.xjrsoft.common.dbmodel.LocalDb;
import com.xjrsoft.common.tsmodel.DetailFrequencyModel;
import com.xjrsoft.common.tsmodel.TsSchemeModel;
import com.xjrsoft.core.constant.AppConstant;
import com.xjrsoft.core.tool.utils.DateUtil;
import com.xjrsoft.core.tool.utils.StringPool;
import com.xjrsoft.core.tool.utils.StringUtil;
import com.xjrsoft.module.base.service.IXjrBaseDatabaseLinkService;
import com.xjrsoft.module.taskSchedule.service.IXjrTsLogService;
import com.xjrsoft.module.taskSchedule.service.IXjrTsProcessService;
import com.xjrsoft.module.taskSchedule.service.IXjrTsSchemeInfoService;
import com.xjrsoft.module.taskSchedule.service.IXjrTsSchemeService;
import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.quartz.*;
import org.quartz.impl.triggers.SimpleTriggerImpl;
import org.quartz.spi.MutableTrigger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.util.*;
import java.util.Calendar;
import java.util.stream.Collectors;

@Slf4j
@Component
public class TsHelper {

    @Autowired
    @Qualifier("schedulerFactoryBean")//多个实现类时指定名字
    private SchedulerFactoryBean schedulerFactoryBean;

    @Autowired
    IXjrTsProcessService ixjrTsProcessService;
    @Autowired
    IXjrTsSchemeInfoService ixjrTsSchemeInfoService;
    @Autowired
    IXjrTsSchemeService ixjrTsSchemeService;
    @Autowired
    IXjrTsLogService ixjrTsLogService;
    @Autowired
    IXjrBaseDatabaseLinkService ixjrBaseDatabaselinkService;

    @Value("${xjrsoft.global-config.localDbString}")
    String ilocalDbString;

    @Autowired
    LocalDb idb;

    protected static final Map<String, String> monthMap = new HashMap(20);
    protected static final Map<String, String> dayMap = new HashMap(60);

    static {
        monthMap.put("1", "JAN");
        monthMap.put("2", "FEB");
        monthMap.put("3", "MAR");
        monthMap.put("4", "APR");
        monthMap.put("5", "MAY");
        monthMap.put("6", "JUN");
        monthMap.put("7", "JUL");
        monthMap.put("8", "AUG");
        monthMap.put("9", "SEP");
        monthMap.put("10", "OCT");
        monthMap.put("11", "NOV");
        monthMap.put("12", "DEC");
        dayMap.put("7", "SUN");
        dayMap.put("1", "MON");
        dayMap.put("2", "TUE");
        dayMap.put("3", "WED");
        dayMap.put("4", "THU");
        dayMap.put("5", "FRI");
        dayMap.put("6", "SAT");
    }

    @PostConstruct
    public void registry(){
        scheduler = schedulerFactoryBean.getScheduler();
        xjrBaseDatabaselinkService = ixjrBaseDatabaselinkService;
        xjrTsLogService = ixjrTsLogService;
        xjrTsProcessService = ixjrTsProcessService;
        xjrTsSchemeInfoService = ixjrTsSchemeInfoService;
        xjrTsSchemeService = ixjrTsSchemeService;
        localDbString = ilocalDbString;
        db = idb;
    }
    private static Scheduler scheduler;

    private static IXjrTsProcessService xjrTsProcessService;

    private static IXjrTsSchemeInfoService xjrTsSchemeInfoService;

    private static IXjrTsSchemeService xjrTsSchemeService;

    private static IXjrTsLogService xjrTsLogService;

    private static IXjrBaseDatabaseLinkService xjrBaseDatabaselinkService;

    private static String localDbString;

    private static LocalDb db;


//    private static SchedulerFactory schedulerFactory = new StdSchedulerFactory();

    public static final Map<String, String> WEEK_MAP = new HashMap<String, String>(7);
    static {
        WEEK_MAP.put("1", "MON");
        WEEK_MAP.put("2", "TUE");
        WEEK_MAP.put("3", "WED");
        WEEK_MAP.put("4", "THU");
        WEEK_MAP.put("5", "FRI");
        WEEK_MAP.put("6", "SAT");
        WEEK_MAP.put("7", "SUN");
    }

    /**
     * @title  添加任务
     * @Description: 添加一个定时任务
     */
    public static void addJob(String processId, TsSchemeModel schemeModel) throws SchedulerException {
        String cronExp = null;
        // 1:只执行一次	2：简单重复执行	3：明细频率设置	4：表达式设置
        int executeType = schemeModel.getExecuteType();
        Date startTime = DateUtil.toDate(schemeModel.getStartTime());
        LocalDateTime endDateTime = schemeModel.getEndTime();
        if (endDateTime != null && endDateTime.isBefore(LocalDateTime.now())) {
            throw new RuntimeException("任务结束时间不能小于当前时间！");
        }
        Date endTime = DateUtil.toDate(endDateTime);
        // 构建任务
        ScheduleBuilder scheduleBuilder = null;
        switch (executeType) {
            case 1:
                // 只执行一次，即开始时间就执行
                scheduleBuilder = SimpleScheduleBuilder.simpleSchedule();
                break;
            case 2:
                // 简单重复执行
                Integer simpleValue = schemeModel.getSimpleValue();
                String simpleType = schemeModel.getSimpleType();
                if (StringUtil.equalsIgnoreCase(simpleType, "minute")) {
                    scheduleBuilder = SimpleScheduleBuilder.simpleSchedule().withIntervalInMinutes(simpleValue).repeatForever();
                } else if (StringUtil.equalsIgnoreCase(simpleType, "hours")) {
                    scheduleBuilder = SimpleScheduleBuilder.simpleSchedule().withIntervalInHours(simpleValue).repeatForever();
                } else if (StringUtil.equalsIgnoreCase(simpleType, "day")) {
                    scheduleBuilder = CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withIntervalInDays(simpleValue).withMisfireHandlingInstructionDoNothing();
                } else if (StringUtil.equalsIgnoreCase(simpleType, "week")) {
                    scheduleBuilder = CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withIntervalInWeeks(simpleValue).withMisfireHandlingInstructionDoNothing();
                }
                break;
            case 3:
                // 明细频率执行
                int seq = 1;
                Set<Trigger> triggers = new HashSet<Trigger>();
                for (DetailFrequencyModel frequency : schemeModel.getFrequencyList()) {
                    String day = "?";
                    // 月份拼接
                    List<String> months = frequency.getCarryMounth();
                    String monthStr = months.stream().map(month -> monthMap.get(month)).collect(Collectors.joining(StringPool.COMMA));

                    String week = "?";
                    // day:每天	week：每周的哪几天	month：每月的哪几天
                    String type = frequency.getType();
                    Integer hour = frequency.getHour();
                    Integer minute = frequency.getMinute();
                    // 验证时间
                    DateBuilder.validateHour(hour);
                    DateBuilder.validateMinute(minute);
                    String cronExpressionFormat = "0 " + minute + " " + hour + " %1$s " + monthStr + " %2$s *";
                    if ("day".equalsIgnoreCase(type)) {
                        day = "*";
                    } else if ("week".equalsIgnoreCase(type)) {
                        String carryDate = StringUtil.isEmpty(frequency.getCarryDate()) ? "*" : frequency.getCarryDate();
                        String[] daysOfWeek = StringUtils.split(frequency.getCarryDate(), StringPool.COMMA);
                        week = Arrays.stream(daysOfWeek).map(dayOfWeek -> dayMap.get(dayOfWeek)).collect(Collectors.joining(StringPool.COMMA));
                    } else if ("month".equalsIgnoreCase(type)) {
                        day = frequency.getCarryDate();
                    }
                    // 拼接表达式
                    cronExp = String.format(cronExpressionFormat, day, week);
                    // 创建监听器，每一条频率数据添加一个监听器
                    MutableTrigger trigger = CronScheduleBuilder.cronSchedule(cronExp).withMisfireHandlingInstructionDoNothing().build();
                    trigger.setStartTime(startTime == null ? new Date() : startTime);
                    trigger.setEndTime(endTime == null ? new Date(Long.MAX_VALUE) : endTime);
                    trigger.setKey(new TriggerKey(processId + StringPool.UNDERSCORE + seq, AppConstant.APPLICATION_GROUP_NAME));
                    triggers.add(trigger);
                    seq++;
                }
                // 如果任务存在，则移除重新添加
                JobKey jobKey = new JobKey(processId, AppConstant.APPLICATION_GROUP_NAME);
                if (scheduler.checkExists(jobKey)) {
                    scheduler.interrupt(jobKey);
                    scheduler.deleteJob(jobKey);
                }
                addJob(processId, triggers);
                return;
            case 4:
                // 表达式设置
                scheduleBuilder = CronScheduleBuilder.cronSchedule(schemeModel.getCornValue()).withMisfireHandlingInstructionDoNothing();
                break;
            default:
                break;
        }
        // 开始时间
        TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger().withSchedule(scheduleBuilder)
                .withIdentity(processId, AppConstant.APPLICATION_GROUP_NAME);
        Integer startType = schemeModel.getStartType();
        if (Integer.valueOf(1).equals(startType) || startTime == null) {
            triggerBuilder.startNow();
        } else {
            triggerBuilder.startAt(startTime);
        }
        // 结束时间
        Integer endType = schemeModel.getEndType();
        if (Integer.valueOf(1).equals(endType) || endTime == null) {
            triggerBuilder.endAt(new Date(Long.MAX_VALUE));
        } else {
            triggerBuilder.endAt(endTime);
        }
        addJob(processId, triggerBuilder.build());
    }

    /**
     * @author 湘北智造-框架开发组
     * @date：2020年08月20日
     * @description：构建定时表达式中按周执行的表达式
     * @param weekDays 星期，e.g: "1,2,3"
     * @return	e.g: "MON,TUE,WED"
     */
    private static String buildWeekCronExp(String weekDays) {
        StringBuilder sb = new StringBuilder(" ");
        String[] array = weekDays.split(",");
        for (String str : array) {
            sb.append(WEEK_MAP.get(str));
            sb.append(",");
        }
        return sb.delete(sb.length() - 1, sb.length()).toString();
    }

    /**
     * @author 湘北智造-框架开发组
     * @date：2021年08月20日
     * @description：构建定时任务触发器
     * @param name	任务名称（唯一id）
     * @param group	任务组名
     * @param cronExpression	定时表达式
     * @return
     */
    public static Trigger buildTriger(String name, String group, String cronExpression) {
        // 表达式调度构建器
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);

        // 按cronExpression表达式构建一个新的trigger
        return TriggerBuilder.newTrigger().withIdentity(name, group)
                .withSchedule(scheduleBuilder).build();
    }

    /**
     * @author 湘北智造-框架开发组
     * @date：2019年10月31日
     * @description：查询定时任务是否存在
     * @param name	定时任务唯一名称
     * @param group	定时任务组名
     * @return
     */
    public static boolean checkExists(String name, String group) {
        try {
            return scheduler.checkExists(new JobKey(name, group));
        } catch (SchedulerException e) {
            log.error("Fail to check job existing.", e);
        }
        return false;
    }

    /**
     * @author 湘北智造-框架开发组
     * @date：2019年10月31日
     * @description：移除定时任务
     * @param name	定时任务唯一名称
     * @param group	定时任务组名
     */
    public static boolean removeJob(String name, String group) {
        JobKey jobKey = new JobKey(name, group);
        try {
            scheduler.interrupt(jobKey);
            scheduler.deleteJob(jobKey);
            return true;
        } catch (SchedulerException e) {
            log.error("Fail to remove job, jobKey: " + jobKey.toString(), e);
        }
        return false;
    }

    /**
     * @author 湘北智造-框架开发组
     * @date：2019年10月30日
     * @description：添加定时任务到插件中
     * @param triggers	给定时任务的多个触发器
     */
    public static void addJob(String processId, Set<? extends Trigger> triggers) {
        try {
            JobDetail jobDetail = JobBuilder.newJob(SchedulingHelper.class).withIdentity(processId, AppConstant.APPLICATION_GROUP_NAME).build();
            jobDetail.getJobDataMap().put("processId", processId);
            try {
                scheduler.scheduleJob(jobDetail, triggers, false);
            } catch (Exception e) {
                log.error("开启定时任务失败！", e);
            }
        } catch (Exception e) {
        }
    }

    /**
     * @author 湘北智造-框架开发组
     * @date：2019年10月25日
     * @description：	添加定时任务到插件中
     */
    public static void addJob(String processId, Trigger trigger) {
        JobDetail jobDetail = JobBuilder.newJob(SchedulingHelper.class).withIdentity(processId, AppConstant.APPLICATION_GROUP_NAME).build();
        addJob(processId, trigger, jobDetail);
    }

    public static void addJob(String processId, Trigger trigger, JobDetail jobDetail) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(processId, AppConstant.APPLICATION_GROUP_NAME);
            // 不存在，创建一个
            if (scheduler.checkExists(trigger.getKey())) {
                // 把原有的任务先删除，再添加新的
                scheduler.deleteJob(trigger.getJobKey());
            }

            try {
                jobDetail.getJobDataMap().put("processId", processId);
                scheduler.scheduleJob(jobDetail, trigger);
            } catch (Exception e) {
                log.error("开启定时任务失败！", e);
                throw new RuntimeException(e);
            }
        } catch (Exception e) {
            log.error("构建定时任务失败！", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * @title  新增单次任务
     * @param processId 任务Id
     * @param startTime 开始时间
     * @param endTime 结束时间
     * */
    @SneakyThrows
    public static void addOnlyOneJob(String processId, LocalDateTime startTime,LocalDateTime endTime){

        JobDataMap jobDataMap = new  JobDataMap();
        jobDataMap.put("processId", processId);

        JobDetail jobDetail= JobBuilder.newJob(SchedulingHelper.class).withIdentity(processId, AppConstant.APPLICATION_GROUP_NAME)
                .usingJobData(jobDataMap)
                .build();

        // 创建一个时间触发器
        SimpleTriggerImpl trigger = new SimpleTriggerImpl();
        trigger.setName(processId);
        trigger.setGroup(AppConstant.APPLICATION_GROUP_NAME);
        trigger.setStartTime(startTime == null ? DateUtil.now() : DateUtil.toDate(startTime));
        trigger.setEndTime(endTime == null ? DateUtil.now() : DateUtil.toDate(endTime));
        trigger.setRepeatCount(0);

        scheduler.scheduleJob(jobDetail,trigger);

        if(!scheduler.isStarted()){
            scheduler.start();
        }

    }

    /**
     * @title  新增单次任务
     * @param processId 任务Id
     * @param startTime 开始时间
     * @param endTime 结束时间
     * @param value 间隔值
     * @param type 间隔类型
     * */
    @SneakyThrows
    public static void addRepeatJob(String processId, LocalDateTime startTime,LocalDateTime endTime,int value,String type){
        JobDataMap jobDataMap = new  JobDataMap();
        jobDataMap.put("xjrTsProcessService",xjrTsProcessService);
        jobDataMap.put("xjrTsSchemeInfoService",xjrTsSchemeInfoService);
        jobDataMap.put("xjrTsSchemeService",xjrTsSchemeService);
        jobDataMap.put("xjrTsLogService",xjrTsLogService);
        jobDataMap.put("xjrBaseDatabaselinkService",xjrBaseDatabaselinkService);
        jobDataMap.put("localDbString",localDbString);
        jobDataMap.put("db",db);
        jobDataMap.put("processId", processId);


        // 创建一个时间触发器
        SimpleTrigger trigger = null;

        switch (type){
            case "minute":
                trigger = TriggerBuilder.newTrigger()
                        .withIdentity(processId, AppConstant.APPLICATION_GROUP_NAME)
                        .startAt(DateUtil.toDate(startTime == null ? LocalDateTime.now() : startTime))
                        .endAt(DateUtil.toDate(endTime == null ? LocalDateTime.MAX : endTime))
                        .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInMinutes(value).repeatForever())
                        .build();
                break;
            case "hours":
                trigger = TriggerBuilder.newTrigger()
                        .withIdentity(processId, AppConstant.APPLICATION_GROUP_NAME)
                        .startAt(DateUtil.toDate(startTime == null ? LocalDateTime.now() :startTime))
                        .endAt(DateUtil.toDate(endTime == null ? LocalDateTime.MAX : endTime))
                        .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInHours(value).repeatForever())
                        .build();
                break;
            case "day"://
                trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
                        .withIdentity(processId, AppConstant.APPLICATION_GROUP_NAME)
                        .startAt(DateUtil.toDate(startTime == null ? LocalDateTime.now() :startTime))
                        .endAt(DateUtil.toDate(endTime == null ? LocalDateTime.MAX : endTime))
                        .withSchedule(CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withIntervalInDays(value))
                        .build();
                break;
            case "week"://
                trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
                        .withIdentity(processId, AppConstant.APPLICATION_GROUP_NAME)
                        .startAt(DateUtil.toDate(startTime == null ? LocalDateTime.now() :startTime))
                        .endAt(DateUtil.toDate(endTime == null ? LocalDateTime.MAX : endTime))
                        .withSchedule(CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withIntervalInWeeks(value))
                        .build();
                break;
        }

        jobDataMap.put("finalFireTime", trigger.getFinalFireTime());
        JobDetail jobDetail= JobBuilder.newJob(SchedulingHelper.class).withIdentity(processId, AppConstant.APPLICATION_GROUP_NAME)
                .usingJobData(jobDataMap)
                .build();

        scheduler.scheduleJob(jobDetail,trigger);

        if(!scheduler.isStarted()){
            scheduler.start();
        }



    }

    /**
     * @title  自定义频率执行
     * @param processId 任务Id
     * @param startTime 开始时间
     * @param endTime 结束时间
     * @param cronlist cron表达式列表
     * */
    @SneakyThrows
    public static void addListCronJob(String processId, LocalDateTime startTime, LocalDateTime endTime, List<String> cronlist){

        JobDataMap jobDataMap = new  JobDataMap();
        jobDataMap.put("xjrTsProcessService",xjrTsProcessService);
        jobDataMap.put("xjrTsSchemeInfoService",xjrTsSchemeInfoService);
        jobDataMap.put("xjrTsSchemeService",xjrTsSchemeService);
        jobDataMap.put("xjrTsLogService",xjrTsLogService);
        jobDataMap.put("xjrBaseDatabaselinkService",xjrBaseDatabaselinkService);
        jobDataMap.put("localDbString",localDbString);
        jobDataMap.put("db",db);
        jobDataMap.put("processId", processId);

        JobDetail jobDetail= JobBuilder.newJob(SchedulingHelper.class).withIdentity(processId, AppConstant.APPLICATION_GROUP_NAME)
                .usingJobData(jobDataMap)
                .build();

        scheduler.addJob(jobDetail,true);

        for (int i =0;i < cronlist.size();i ++ ){
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(processId, AppConstant.APPLICATION_GROUP_NAME)
                    .startAt(DateUtil.toDate(startTime))
                    .endAt(DateUtil.toDate(endTime))
                    .withSchedule(CronScheduleBuilder.cronSchedule(cronlist.get(i)))
                    .forJob(jobDetail)
                    .build();

            scheduler.scheduleJob(jobDetail,trigger);
        }

//        // 创建一个时间触发器
//        cronlist.forEach(item -> {
//            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, AppConstant.APPLICATION_GROUP_NAME)
//                    .startAt(DateUtil.toDate(startTime))
//                    .endAt(DateUtil.toDate(endTime))
//                    .withSchedule(CronScheduleBuilder.cronSchedule(item))
//                    .forJob(jobDetail)
//                    .build();
//
//            try {
//                scheduler.scheduleJob(jobDetail,trigger);
//            }
//            catch (Exception e){
//                throw new Exception("自定义频率执行错误！");
//            }
//
//        });

        if(scheduler.isStarted()){
            scheduler.start();
        }

    }

    /**
     * @title  自定义频率执行
     * @param processId 任务Id
     * @param startTime 开始时间
     * @param endTime 结束时间
     * @param cron cron表达式
     * */
    @SneakyThrows
    public static void addCronJob(String processId, LocalDateTime startTime, LocalDateTime endTime, String cron){

        JobDataMap jobDataMap = new  JobDataMap();
        jobDataMap.put("xjrTsProcessService",xjrTsProcessService);
        jobDataMap.put("xjrTsSchemeInfoService",xjrTsSchemeInfoService);
        jobDataMap.put("xjrTsSchemeService",xjrTsSchemeService);
        jobDataMap.put("xjrTsLogService",xjrTsLogService);
        jobDataMap.put("xjrBaseDatabaselinkService",xjrBaseDatabaselinkService);
        jobDataMap.put("localDbString",localDbString);
        jobDataMap.put("db",db);
        jobDataMap.put("processId", processId);

        JobDetail jobDetail= JobBuilder.newJob(SchedulingHelper.class).withIdentity(processId, AppConstant.APPLICATION_GROUP_NAME)
                .usingJobData(jobDataMap)
                .build();


        // 创建一个时间触发器
        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(processId, AppConstant.APPLICATION_GROUP_NAME)
                .startAt(DateUtil.toDate(startTime))
                .endAt(DateUtil.toDate(endTime))
                .withSchedule(CronScheduleBuilder.cronSchedule(cron))
                .forJob(jobDetail)
                .build();

        scheduler.scheduleJob(jobDetail,trigger);

        if(scheduler.isStarted()){
            scheduler.start();
        }

    }


    /**
     * @title  删除任务
     * @param jobName 任务名
     * */
    @SneakyThrows
    public static void deleteJob(String jobName){
        TriggerKey key = new TriggerKey(jobName,AppConstant.APPLICATION_GROUP_NAME);
        //暂停触发器
        scheduler.pauseTrigger(key);
        //移除触发器
        scheduler.unscheduleJob(key);
        //删除任务
        scheduler.deleteJob(JobKey.jobKey(jobName,AppConstant.APPLICATION_GROUP_NAME));

    }

    /**
     * @title  暂停任务
     * @param jobName 任务名
     * */
    @SneakyThrows
    public static void pauseJob(@NonNull String jobName){
        //删除任务
        scheduler.pauseJob(JobKey.jobKey(jobName,AppConstant.APPLICATION_GROUP_NAME));

    }

    /**
     * @title  重启任务
     * @param jobName 任务名
     * */
    @SneakyThrows
    public static void resumeJob(@NonNull String jobName){
        //删除任务
        scheduler.resumeJob(JobKey.jobKey(jobName,AppConstant.APPLICATION_GROUP_NAME));

    }


}
